/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
/*
 * Copyright (c) 2019 HUST Dian Group
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation;
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * Original Author: Pengyu Liu <eicliupengyu@gmail.com>
 */
#include "ns3/double.h"
#include "ns3/uinteger.h"
#include "ns3/boolean.h"
#include "ns3/string.h"
#include "ns3/pointer.h"
#include "ns3/log.h"
#include "robo-judge.h"

namespace ns3 {
NS_LOG_COMPONENT_DEFINE ("RoboJudge");
NS_OBJECT_ENSURE_REGISTERED (RoboJudge);
RoboJudge::RoboJudge ()
{
  NS_LOG_FUNCTION (this);
}

RoboJudge::~RoboJudge ()
{
  NS_LOG_FUNCTION (this);
}
TypeId
RoboJudge::GetTypeId (void)
{
  static TypeId tid =
      TypeId ("ns3::RoboJudge")
          .SetParent<Object> ()
          .SetGroupName ("Robo")
          .AddConstructor<RoboJudge> ()
          .AddAttribute ("UpdatePeroid", "Peroid between update", DoubleValue (1e-3f),
                         MakeDoubleAccessor (&RoboJudge::m_updatePeroid),
                         MakeDoubleChecker<float> (0))
          .AddAttribute ("LoseFactor", "Give ammo lose", DoubleValue (0.01f),
                         MakeDoubleAccessor (&RoboJudge::m_loseFac),
                         MakeDoubleChecker<float> (0, 1.f))
          .AddAttribute ("XSize", "X size of boundary", DoubleValue (28),
                         MakeDoubleAccessor (&RoboJudge::m_boundaryX), MakeDoubleChecker<float> (0))
          .AddAttribute ("YSize", "Y size of boundary", DoubleValue (15),
                         MakeDoubleAccessor (&RoboJudge::m_boundaryY), MakeDoubleChecker<float> (0))
          .AddAttribute ("EnablePlot", "Enable plot", BooleanValue (true),
                         MakeBooleanAccessor (&RoboJudge::m_enablePlot), MakeBooleanChecker ())
          .AddAttribute ("PlotPeroid", "Peroid between plot", DoubleValue (1. / 24),
                         MakeDoubleAccessor (&RoboJudge::m_plotPeroid),
                         MakeDoubleChecker<float> (0))
          .AddAttribute ("PlotBufferSize", "Plot buffer size", UintegerValue (65536),
                         MakeUintegerAccessor (&RoboJudge::m_bufferSize),
                         MakeUintegerChecker<uint16_t> (0))
          .AddAttribute ("PlotFileName", "Plot file name", StringValue ("PlotDefault.plt"),
                         MakeStringAccessor (&RoboJudge::m_plotFileName), MakeStringChecker ())
          .AddAttribute ("VisibleDistance", "The visible distance between robos", DoubleValue (5.0),
                         MakeDoubleAccessor (&RoboJudge::m_visibleDistance),
                         MakeDoubleChecker<float> (0))
          .AddAttribute ("VisibleDistanceJitter", "VisibleDistance Jitter",
                         StringValue ("ns3::UniformRandomVariable[Min=-0.5|Max=0.5]"),
                         MakePointerAccessor (&RoboJudge::m_random),
                         MakePointerChecker<RandomVariableStream> ());
  ;
  return tid;
}
void
RoboJudge::Init ()
{
  NS_LOG_FUNCTION (this);
  static bool isInit = false;
  if (isInit)
    return;
  isInit = true;
  if (m_enablePlot)
    {
      Simulator::Schedule (Seconds (m_plotPeroid), &RoboJudge::PlotAll, this);
      Simulator::ScheduleDestroy (&RoboJudge::CloseFile, this);
      m_plotFp = fopen (m_plotFileName.c_str (), "wb");
      NS_ASSERT_MSG (m_plotFp != 0, "Cannot create plot file");
      fprintf (m_plotFp, "bp:%.4f:%.4f\n", m_boundaryX, m_boundaryY);
    }
  Ptr<RoboActor> boundary = CreateObject<RoboActor> ();
  NS_ASSERT_MSG (boundary != NULL, "Boundary error");
  boundary->SetCollisionType (Collision_Type_Boundary);
  boundary->SetSelfMask (SelfMaskBuilding);
  boundary->SetCollisionMask (gBuildingCollisionMask);
  boundary->SetGlobalLocation (FVector ());
  boundary->SetGlobalRotation (FAngle ());
  boundary->AddBoundaryPoint (FVector (0, 0));
  boundary->AddBoundaryPoint (FVector (m_boundaryX, m_boundaryY));
  AddBuilding (boundary);
  Simulator::Schedule (Seconds (m_updatePeroid), &RoboJudge::Update, this);
}
void
RoboJudge::CloseFile ()
{
  NS_LOG_FUNCTION (this);
  if (m_plotFp)
    fclose (m_plotFp);
}
TypeId
RoboJudge::GetInstanceTypeId (void) const
{
  return GetTypeId ();
}
void
RoboJudge::AddRobo (Ptr<RoboBase> robo)
{
  NS_LOG_FUNCTION (this << robo->m_name << "(" << robo->m_team << ")");
  m_roboList.push_back (robo);
  if (m_enablePlot)
    {
      if (!robo->m_initPlot.IsNull ())
        {
          robo->m_initPlot (m_plotFp);
        }
    }
}
void
RoboJudge::AddAmmo (Ptr<RoboActor> ammo)
{
  NS_LOG_FUNCTION (this);
  m_ammoList.push_back (ammo);
}
void
RoboJudge::AddBuilding (Ptr<RoboActor> building)
{
  NS_LOG_FUNCTION (this);
  m_buildingList.push_back (building);
  if (m_enablePlot)
    {
      if (!building->m_initPlot.IsNull ())
        {
          building->m_initPlot (m_plotFp);
        }
    }
}
bool
RoboJudge::GiveAmmoTo (uint8_t sourceId, uint8_t targetId, int num, AmmoType ammoType)
{
  NS_LOG_FUNCTION (this << (int) sourceId << "  " << (int) targetId << "  " << num << "  "
                        << (int) ammoType);
  Ptr<RoboBase> source = m_roboList[sourceId];
  Ptr<RoboBase> target = m_roboList[targetId];
  if (source->m_roboType != RoboTypeEngineer)
    return false;
  RoboWeapon &sourceWeapon = source->m_weapon[ammoType];
  RoboWeapon &targetWeapon = target->m_weapon[ammoType];
  if (sourceWeapon.m_ammoNumber < num)
    return false;
  sourceWeapon.m_ammoNumber -= num;
  targetWeapon.m_ammoNumber +=
      num * std::max (0., 1. - m_loseFac * std::pow (source->m_globalLocation.GetDistance2 (
                                                         target->m_globalLocation),
                                                     4.));
  if (targetWeapon.m_ammoNumber > gWeaponMaxAmmoNum[target->m_roboType][ammoType])
    targetWeapon.m_ammoNumber = gWeaponMaxAmmoNum[target->m_roboType][ammoType];
  return true;
}
void
RoboJudge::Update ()
{
  NS_LOG_FUNCTION (this);
  for (auto &ammo : m_ammoList)
    {
      ammo->Update (m_updatePeroid);
    }
  for (auto &robo : m_roboList)
    {
      robo->Update (m_updatePeroid);
    }

  //Handle collision
  for (auto i = m_ammoList.begin (); i != m_ammoList.end ();)
    {
      if ((*i)->m_isDeleted)
        {
          m_ammoList.erase (i++);
        }
      else
        {
          ++i;
        }
    }
  for (auto &building : m_buildingList)
    {
      for (auto &robo : m_roboList)
        {
          if (IsCollision (building, robo))
            {
              robo->IndicateCollision (building);
              building->IndicateCollision (robo);
            }
        }
      for (auto &ammo : m_ammoList)
        {
          if (IsCollision (building, ammo))
            {
              ammo->IndicateCollision (building);
              building->IndicateCollision (ammo);
            }
        }
    }
  for (uint32_t i = 0; i < m_roboList.size (); ++i)
    {
      for (uint32_t j = i + 1; j < m_roboList.size (); ++j)
        {
          if (IsCollision (m_roboList[i], m_roboList[j]))
            {
              m_roboList[i]->IndicateCollision (m_roboList[j]);
              m_roboList[j]->IndicateCollision (m_roboList[i]);
            }
        }
      for (auto &ammo : m_ammoList)
        {
          if (IsCollision (m_roboList[i], ammo))
            {
              m_roboList[i]->IndicateCollision (ammo);
              ammo->IndicateCollision (m_roboList[i]);
            }
        }
    }

  //Indicate location
  for (uint32_t i = 0; i < m_roboList.size (); ++i)
    {
      float curVisibleDistance = m_visibleDistance + m_random->GetValue ();
      curVisibleDistance *= curVisibleDistance;
      for (uint32_t j = i + 1; j < m_roboList.size (); ++j)
        {
          if (m_roboList[i]->m_globalLocation.GetDistance2 (m_roboList[j]->m_globalLocation) <=
              curVisibleDistance)
            {
              m_roboList[i]->IndicateLocation (m_roboList[j]);
              m_roboList[j]->IndicateLocation (m_roboList[i]);
            }
        }
    }
  Simulator::Schedule (Seconds (m_updatePeroid), &RoboJudge::Update, this);
}

void
RoboJudge::PlotAll ()
{
  NS_LOG_FUNCTION (this);
  auto ts = Simulator::Now ().GetMicroSeconds ();
  fprintf (m_plotFp, "ts:%lu\n", ts);
  for (auto &ammo : m_ammoList)
    {
      if (!ammo->m_updatePlot.IsNull ())
        {
          ammo->m_updatePlot (m_plotFp);
        }
    }
  for (auto &robo : m_roboList)
    {
      if (!robo->m_updatePlot.IsNull ())
        {
          robo->m_updatePlot (m_plotFp);
        }
    }
  for (auto &building : m_buildingList)
    {
      if (!building->m_updatePlot.IsNull ())
        {
          building->m_updatePlot (m_plotFp);
        }
    }
  Simulator::Schedule (Seconds (m_plotPeroid), &RoboJudge::PlotAll, this);
}
void
RoboJudge::IndicateShoot (uint8_t shooter, uint8_t target, int injury)
{
  static float extExp = 5.f; //first blood
  static char outputbuffer[1024];
  Ptr<RoboBase> shooterPtr = m_roboList[shooter];
  Ptr<RoboBase> targetPtr = m_roboList[target];
  if (targetPtr->m_isDestroy || targetPtr->m_invincible)
    return;
  m_shootTime[target][shooter] = Simulator::Now ();
  if (targetPtr->m_virtualShield > 0.f)
    {
      auto dec = std::min (injury, targetPtr->m_virtualShield);
      injury -= dec;
      targetPtr->m_virtualShield -= dec;
      targetPtr->m_virtualShieldTimer.Cancel ();
      targetPtr->m_virtualShieldTimer.Schedule ();
    }
  targetPtr->m_life -= injury;
  targetPtr->UpdateStatus ();
  if (targetPtr->m_isDestroy)
    {
      //Handle invincible
      if (targetPtr->m_roboType == RoboTypeSentinel)
        {
          for (auto &robo : m_roboList)
            {
              if (robo->m_roboType == RoboTypeTower && robo->m_team == targetPtr->m_team)
                {
                  robo->m_invincible = false;
                  break;
                }
            }
        }
      else if (targetPtr->m_roboType == RoboTypeEngineer)
        {
          Simulator::Schedule (Seconds (20), &RoboJudge::Resurrection, this, 255, targetPtr->m_uid);
        }
      char *outPtr = outputbuffer;
      if (m_enablePlot)
        {
          outPtr += sprintf (outPtr, "rd:%u@%u", target, shooter);
        }
      //Handle exp
      auto &shootTime = m_shootTime[target];
      auto now = Simulator::Now ();
      shooterPtr->m_exp += targetPtr->m_exp + extExp;
      extExp = 0.f;
      shooterPtr->UpdateStatus ();
      for (auto each : shootTime)
        {
          if (now - each.second > Seconds (10.))
            continue;
          m_roboList[each.first]->m_exp +=
              m_roboList[each.first]->m_team != targetPtr->m_team ? targetPtr->m_exp * .25f : 0.f;
          m_roboList[each.first]->UpdateStatus ();
          if (m_enablePlot && each.first != shooter)
            {
              outPtr += sprintf (outPtr, "|%u", each.first);
            }
        }
      if (m_enablePlot)
        {
          fprintf (m_plotFp, "%s\n", outputbuffer);
        }
      shootTime.clear ();
    }
}
void
RoboJudge::IndicateRoboUpgrade (uint8_t uid)
{
  if (m_enablePlot)
    {
      fprintf (m_plotFp, "ru:%u@%d\n", uid, m_roboList[uid]->m_level + 1);
    }
}
void
RoboJudge::IndicateLifeUpgrade (uint8_t uid)
{
  if (m_enablePlot)
    {
      fprintf (m_plotFp, "lu:%u@%d@%d\n", uid, m_roboList[uid]->m_maxLifeLevel,
               gRoboMaxLife[m_roboList[uid]->m_roboType][m_roboList[uid]->m_maxLifeLevel]);
    }
}
void
RoboJudge::IndicateMaxAmmoSpeedUpgrade (uint8_t uid)
{
  if (m_enablePlot)
    {
      fprintf (m_plotFp, "au:%u@%d@%.3f@%.3f\n", uid, m_roboList[uid]->m_maxAmmoSpeedLevel,
               gWeaponMaxSpeed[0][m_roboList[uid]->m_maxAmmoSpeedLevel],
               gWeaponMaxSpeed[1][m_roboList[uid]->m_maxAmmoSpeedLevel]);
    }
}

void
RoboJudge::IndicateKillSelf (uint8_t uid)
{
  m_shootTime[uid].clear ();
  if (m_enablePlot)
    {
      fprintf (m_plotFp, "ks:%u\n", uid);
    }
}

RoboInfo
RoboJudge::QueryRobo (uint8_t uid)
{
  Ptr<RoboBase> robo = m_roboList[uid];
  return (RoboInfo){uid, robo->m_team, robo->m_name, robo->m_roboType, robo->m_isDestroy};
}

void
RoboJudge::Resurrection (uint8_t uid, uint8_t target)
{
  if (uid == 255)
    {
      if (m_enablePlot)
        {
          fprintf (m_plotFp, "rr:%u@%u\n", uid, target);
        }
      m_roboList[target]->DoResurrection ();
      return;
    }
  Ptr<RoboBase> srcPtr = m_roboList[uid];
  Ptr<RoboBase> targetPtr = m_roboList[target];
  if (srcPtr->m_roboType != RoboTypeEngineer ||
      srcPtr->m_globalLocation.GetDistance2 (targetPtr->m_globalLocation) > 1.f)
    {
      return;
    }
  if (m_enablePlot)
    {
      fprintf (m_plotFp, "rr:%u@%u\n", uid, target);
    }
  m_roboList[target]->DoResurrection ();
}
} // namespace ns3
